package de.fhaugsburg.games.boardgames.connect4;

import de.fhaugsburg.games.boardgames.GameState;
import de.fhaugsburg.games.boardgames.PlayerMode;
import de.fhaugsburg.games.boardgames.PlayerState;
import de.fhaugsburg.games.boardgames.exceptions.IllegalMoveException;

public class Connect4Engine {
	
	public static final int PIECES_TO_WIN = 4;
	public static final int NUM_PLAYERS = 2;
	
	
	private int nextPlayerToMove;
	private final int numberOfPlayers = 2;
	private Connect4Board board;
	private PlayerMode playerMode;
	private Connect4Player[] player;
	private IGuiMessage gui;
	private boolean gameRunning = false;
	private GameState gameState;
	private Connect4Player winningPlayer;
	
	

	
	public Connect4Engine(Connect4Board board) {
		//registers the board to play on
		this.board = board;
		this.player = new Connect4Player[NUM_PLAYERS];
		setGameState(GameState.NOGAME);
	}

	
	
	
	/**
	 * registers user interface to do repainting if needed
	 * @param gui
	 */
	public void registerGui(IGuiMessage gui){
		//registers user interface to request input, repaint
		this.gui = gui;		
	}
	
	
	
	

	public boolean isGameRunning() {
		return gameRunning;
	}

	public void setGameRunning(boolean gameRunning) {
		this.gameRunning = gameRunning;
	}

	
	
	
	/**
	 * GUI registered ?
	 * @return
	 */
	private boolean guiAvailable(){
		return (gui != null);
	}

	
	
	/**
	 * this is called whenever user interface needs to be repainted
	 */
	private void requiresRepaint() {
		if (guiAvailable()) gui.repaint();
	}

	
	
	
	/**
	 * this is called whenever a user input is required
	 */
	private void requiresInput(){
		setGameState(GameState.WAITING_FOR_INPUT);
		if (guiAvailable()) gui.idle();
	}
	
		
	
	
	
	
	/**
	 * init new game
	 * @param mode
	 */
	public void newGame(PlayerMode mode) {
		setPlayerMode(mode);
		switch (mode){
		case SINGLE:
			player[0] = new HumanPlayer("Humanplayer", BinaryPiece.X, board);
			player[1] = new CPUPlayer("CPUplayer", BinaryPiece.O, board);
			break;
		case MULTI:
			player[0] = new HumanPlayer("Humanplayer 1", BinaryPiece.X, board);
			player[1] = new HumanPlayer("Humanplayer 2", BinaryPiece.O, board);
			break;
		}
		//first player starts the game
		nextPlayerToMove = 0;
		//wipe the board
		board.reset();
		
	}

	
	
	
	/**
	 * starts the game
	 */
	public void startGame(){
		setGameState(GameState.WAITING_FOR_PLAYER);
		setWinningPlayer(null);
		requiresRepaint();
		makeNextMove();
	}
	
	
	/**
	 * the method
	 */
	private void makeNextMove(){
		if (getGameState() != GameState.GAMEOVER && getGameState() != GameState.NOGAME){
			Connect4Player player = getActivePlayer();
			PlayerState playerState = player.getPlayerState();
			setGameState(GameState.WAITING_FOR_PLAYER);
			switch (playerState){
			case READY:
				setGameState(GameState.THINKING);
				try {
					makeMove(player.getMove());
				} catch (IllegalMoveException e) {
					//the move return should always be valid, otherwise: AI is broken :)
					System.err.println("CPU robot player returned illegal move, something seems to be broken.");
					e.printStackTrace();
				}
				break;
			case WAITING_FOR_INPUT:
				requiresInput();
			}
		}
	}
	
	
	
	
	/**
	 * commits the given move and advances to the next player
	 * @param column
	 * @throws IllegalMoveException
	 */
	public void makeMove(int column) throws IllegalMoveException{
		if (board.isMoveValid(column)){
			Connect4Player player = getActivePlayer();
			board.putPiece(player.getSymbol(), column);
			//checkForWinningCondition(column);
			checkGameOver(column);
			if (getGameState() != GameState.GAMEOVER){
				nextPlayer();
				//board has changed, user interface needs to be repainted
				requiresRepaint();
				makeNextMove();
			}
		}else{
			throw new IllegalMoveException("Move is not valid.");
		}
	}
	
	
	
	/**
	 * returns currently moving player
	 * @return
	 */
	public Connect4Player getActivePlayer(){
		return player[nextPlayerToMove];
	}
	
	
	
	
	
	/**
	 * next players turn in local variable
	 */
	private void nextPlayer(){
		setGameState(GameState.WAITING_FOR_PLAYER);
		nextPlayerToMove = (nextPlayerToMove + 1) % numberOfPlayers;	
	}
	
	
	
	/**
	 * return if given move wins the game
	 * @param column
	 * @return
	 */
	private boolean checkForWinningCondition(int column){
		//board.countEqualPiecesInOneDirection(board.getRowOfColumn(column), column, getActivePlayer().getSymbol(), new int[]{1,0});
//		System.err.println(board.countEqualPiecesInARow(column, getActivePlayer().getSymbol()));
		if (board.countEqualPiecesInARow(column, getActivePlayer().getSymbol()) >= PIECES_TO_WIN){
			return true;
		}else{
			return false;
		}
	}
	
	
	
	
	
	/**
	 * action for gameover condition
	 * @param column
	 */
	private void checkGameOver(int column){
		if(checkForWinningCondition(column)){
			setGameState(GameState.GAMEOVER);
			setWinningPlayer(getActivePlayer());
			requiresRepaint();
		}
	}


	/**
	 * sets the state of the game
	 * @param gameState
	 */
	private void setGameState(GameState gameState) {
		this.gameState = gameState;
	}



	/**
	 * current state of game
	 * @return
	 */
	public GameState getGameState() {
		return gameState;
	}


	private void setPlayerMode(PlayerMode playerMode) {
		this.playerMode = playerMode;
	}


	public PlayerMode getPlayerMode() {
		return playerMode;
	}


	private void setWinningPlayer(Connect4Player winningPlayer) {
		this.winningPlayer = winningPlayer;
	}


	public Connect4Player getWinningPlayer() {
		return winningPlayer;
	}
	
}